#include "Population.h"
#include "ChromosomeFactory.h"
#include "Utils/GlobalCppRandomEngine.h"
#include <iostream>
#include <algorithm>

namespace GeneticAlgorithm {

    Population::Population(unsigned long numberOfChromosome) {
        this->chromosomeArray = new Chromosome*[2 * numberOfChromosome];
        for (unsigned i = 0; i < 2 * numberOfChromosome; i++) {
            this->chromosomeArray[i] = nullptr;
        }
        this->numberOfChromosome = numberOfChromosome;
    }

    Population::~Population() {
        for (unsigned long i = 0; i < 2 * this->numberOfChromosome; i++) {
            if (nullptr != this->chromosomeArray[i]) {
                delete this->chromosomeArray[i];
            }
        }
        delete[] this->chromosomeArray;
    }

    bool Population::setChromosome(unsigned long offset, Chromosome *chromosome) {
        unsigned long buffOffset = this->isLeftUse ? 0 : this->numberOfChromosome;
        if (offset >= this->numberOfChromosome) {
            return false;
        }
        if (nullptr == this->chromosomeArray[buffOffset + offset]) {
            this->chromosomeArray[buffOffset + offset] = chromosome;
            if (this->isMaxFitnessChromosomeCache && this->maxFitnessChromosomeCache->getFitness() < chromosome->getFitness()) {
                this->maxFitnessChromosomeCache = chromosome;
            }
            return true;
        }
        Chromosome* origin = this->chromosomeArray[buffOffset + offset];
        if ((void*)origin == (void*)chromosome) {
            return true;
        }
        if (this->isMaxFitnessChromosomeCache && this->maxFitnessChromosomeOffset == offset) {
            if (this->maxFitnessChromosomeCache->getFitness() > chromosome->getFitness()) {
                this->isMaxFitnessChromosomeCache = false;
            }// else {
                //this->maxFitnessChromosomeCache = chromosome; 因为 origin->copyDataFrom(chromosome);
            //}
        }
        if (this->isMaxFitnessChromosomeCache && this->maxFitnessChromosomeOffset != offset) {
            if (chromosome->getFitness() > this->maxFitnessChromosomeCache->getFitness()) {
                //this->maxFitnessChromosomeCache = chromosome; 因为 origin->copyDataFrom(chromosome);
                this->maxFitnessChromosomeOffset = offset;
            }
        }
        origin->copyDataFrom(chromosome);
        return true;
    }

    bool Population::replaceChromosome(unsigned long offset, Chromosome *chromosome) {
        return this->setChromosome(offset, chromosome);
    }

    Chromosome* Population::getChromosome(unsigned long offset) {
        if (offset >= this->numberOfChromosome) {
            throw "Error, offset out of range, in \"Population::getChromosome\".";
        }
        unsigned long buffOffset = this->isLeftUse ? 0 : this->numberOfChromosome;
        if (!this->chromosomeArray[buffOffset + offset]) {
            throw "Null pointer exception.";
        }
        return this->chromosomeArray[buffOffset + offset];
    }

    unsigned long Population::getSize() {
        return this->numberOfChromosome;
    }

    Chromosome* Population::getMaxFitnessChromosome() {
        if (this->isMaxFitnessChromosomeCache) {
            return this->maxFitnessChromosomeCache;
        }
        long double maxFitness = std::numeric_limits<long double>::min();
        unsigned long buffOffset = this->isLeftUse ? 0 : this->numberOfChromosome;
        unsigned long offset = 0;
        this->maxFitnessChromosomeCache = this->chromosomeArray[buffOffset + offset];
        for (unsigned long i = 0; i < this->numberOfChromosome; i++) {
            if (this->chromosomeArray[buffOffset + i]->getFitness() > maxFitness) {
                offset = i;
                maxFitness = this->chromosomeArray[buffOffset + i]->getFitness();
            }
        }
        this->isMaxFitnessChromosomeCache = true;
        this->maxFitnessChromosomeCache = this->chromosomeArray[buffOffset + offset];
        this->maxFitnessChromosomeOffset = offset;
        return this->maxFitnessChromosomeCache;
    }

    void Population::nextGeneration(long double r) {
        unsigned long leftPartOffset = this->isLeftUse ? 0 : this->numberOfChromosome;
        unsigned long rightPartOffset = this->isLeftUse ? this->numberOfChromosome : 0;
        unsigned long select1;
        unsigned long select2;
        Chromosome *s1, *s2, *dst;
        this->sort();
        this->chromosomeArray[rightPartOffset]->copyDataFrom(this->maxFitnessChromosomeCache);
        for (unsigned long i = 1; i < this->numberOfChromosome; i++) {
            select1 = i - 1;
            select2 = i;
            s1 = this->chromosomeArray[leftPartOffset + select1];
            s2 = this->chromosomeArray[leftPartOffset + select2];
            dst = this->chromosomeArray[rightPartOffset + i];
            s1->crossoverTo(s2, dst);
            dst->mutation(r);
        }
        this->isLeftUse = !(this->isLeftUse);
        this->isMaxFitnessChromosomeCache = false;
    }

    void Population::useChromosomeFactoryIfNeed(ChromosomeFactory* factory, unsigned long lengthOfChromosome) {
        for (unsigned long i = 0; i < 2 * this->numberOfChromosome; i++) {
            if (nullptr == this->chromosomeArray[i]) {
                this->chromosomeArray[i] = factory->buildEmpty(lengthOfChromosome);
            }
        }
    }

    void Population::sort() {
        unsigned long buffOffset = this->isLeftUse ? 0 : this->numberOfChromosome;
        std::sort(
            &(this->chromosomeArray[buffOffset]),
            &(this->chromosomeArray[buffOffset + this->numberOfChromosome]),
            [](Chromosome* a, Chromosome* b) -> bool {
                return a->getFitness() > b->getFitness();
            }
        );
        this->isMaxFitnessChromosomeCache = true;
        this->maxFitnessChromosomeCache = this->chromosomeArray[buffOffset];
        this->maxFitnessChromosomeOffset = 0;
    }
}
